\chapter{Makefile}
\section{Makefile基础}
Makefile是一款非常著名的项目管理软件，虽然说它不好的人很多但用它的人更多。你能角的出名字的大公司基本都用它。Makefile最基本的单位是目标，最基本的关系是依赖。一个项目可以有多个目标组成，其中每一个目标又依赖于很多个.o文件。每一个.o文件依赖于.c文件。根据这些依赖关系可以支撑一个森林(多棵树)。如果希望生成其中任何一个目标只要从树顶开始进行后序编译即可。
\subsection{Makefile基础}
接下来我们从一个简单的设计多文件的程序来更具体的理解Makefile的工作方式。
\begin{lstlisting}[language=make]
CFLAGS = -c
default: hello
hello : hello.o lib.o
   ld  ${LDFLAGS} hello.o lib.o -o hello

hello.o: hello.c
   gcc ${CFLAGS} hello.c -o hello.o

lib.o: lib.c
   gcc ${CFLAGS} lib.c -o lib.o
\end{lstlisting}

正如之前提到的，这段代码可以用森林表示，但这森林很小仅仅有一个小树：


\qtreecentertrue 
\Tree [.default [.hello [.hello.o \textit{gcc hello.c -o hello.o} ] 
                        [.lib.o \textit{gcc lib.c -o lib.o} ] ] ]


Makefile的内部实现基本上也是以树为组织形式，而后通过后续遍历。
按照先生成依赖项而后生成自身的顺序完成整个工程的构建。


此外，make还有一个很好的特性：它会监控每一个依赖项及target的变化。如果其中任何一个改变，那么所有依赖于它的项（包括直接依赖及间接依赖）都会被重新生成。这个特性很好，尤其是当项目设计非常多的文件的时候，如果一个文件变化导致整个项目重新build那就太耗时了。
\subsection{伪目标}
实际上Makefile会将所有的target及依赖项当作文件，如果目标不是一个文件会怎么样呢？make会把它当作文件并理所当然的认为它被存在是因为没有被生成，因此这个目标总是被执行。最常见的伪目标是clean。例如给上面的Makefile加上clean:
\begin{lstlisting}[language=make]
CFLAGS = -c
default: hello
hello : hello.o lib.o
   ld  ${LDFLAGS} hello.o lib.o -o hello

hello.o: hello.c
   gcc ${CFLAGS} hello.c -o hello.o

lib.o: lib.c
   gcc ${CFLAGS} lib.c -o lib.o

clean:
    rm -f *.o
    rm -f hello
.PHONY: clean
\end{lstlisting}
每当我们运行make clean的时候，两条rm命令都会执行，因为clean是不存的。make提供了一个特殊的指令叫做.PHONY，它可以用于表示伪目标，但一般不会这么做。

=======
Makefile的内部实现基本上也是以树为组织形式，而后通过后续遍历。
按照先生成依赖项而后生成自身的顺序完成整个工程的构建。

此外，make还有一个很好的特性：它会监控每一个依赖项及target的变化。如果其中任何一个改变，那么所有依赖于它的项（包括直接依赖及间接依赖）都会被重新生成。这个特性很好，尤其是当项目设计非常多的文件的时候，如果一个文件变化导致整个项目重新build那就太耗时了。
>>>>>>> 5c2f5d07ad8ae51d178068bc67ca0b3068148a4c
\subsection{Makefile变量}
和众多脚本语言一样，Makefile怎么可能没有变量呢。上面的例子就有两个变量\$\{LDFLAGS\}和变量\$\{CFLAGS\}。
变量定义的格式比较直接一个赋值运算符就可以搞定了。

GNU Make当中包含两种复制运算符“=”和“:=”。这两个运算符还是有一些差异的。赋值符号“:=“会立刻计算右边的字符串并赋值给变量。而运算符“=”则会仅仅保存表达式，在用到变量的一刻，make程序会计算(evaluate)它的值并加入到运算当中。我们通过一个例子来了解一下他们的不同。这非常重要，因为Linux的Makefile当中这种技巧用了很多。

\subsection{Makefile函数调用}
如果单单认为Makefile就是这么简单的话，恐怕会让大家失望。
如果只是这么简单，我们也就不用单独开辟一章来介绍Linux Kernel的Makefile了。
实际上Makefile包含非常多的强大功能，对于很多支持多平台的软件来说，这些功能都是不可或缺的。
接下来我们介绍函数调用。
\subsection{Makefile流程控制}
大部分介绍编程语言书籍来都倾向于将流程控制放在函数之前，这这里我们却不得不成为一个例外。
原因很简单，Makefile的流程控制是基于函数调用的，它并没有关键字if，或是关键字for，
它拥有的是两个函数if和foreach。
这两个函数是Makefile流程控制的基础。
\subsubsection{if}
\subsubsection{foreach}
还是通过例子来学习它吧
\begin{lstlisting}[language=make]
srcs := a.c b.c c.c
files := $(foreach,n,${srcs},$(shell gcc -c ${n} -o ${n}.o))
\end{lstlisting}
函数for有4个参数
\begin{itemize}
\item n 用于枚举，代表一次迭代从参数srcs当中取出的值
\item \$\{srcs\} 用于枚举的对象
\item 循环体 \$(shell gcc \-c \$\{n\} \-o \$\{n\}.o) 
\end{itemize}

\section{Linux构建系统介绍}
\subsection{kbuild Makefile基本语法}
Linux Kernel的Makefile通过变量来记录那些文件需要build以及文件作为什么的组成部分。Linux Kerne的Makefiles在官方文档上被曾作kbuild Makefiles，我们也称他为kbuild Makefiles。
大致上，内核的不表包括两种built-in以及module。
build-in代表的是整个内核，如果文件需要编译进到vmlinux这个ELF文件，那么通常它会在Makefile当中写作
 \begin{lstlisting}[language=make]
  obj-y += foo.o
 \end{lstlisting}
否则话可能写作
 \begin{lstlisting}[language=make]
  obj-m += foo.o
 \end{lstlisting}
当然还有一种情况就是根据用户的配置进行生成
 \begin{lstlisting}[language=make]
  obj-${CONFIG_FOO} += foo.o
 \end{lstlisting}
如果CONFIG\_FOO被配置成y, 那么他就被直接编译到built\-in当中，如果配置为m，则别生成为可加载的内容。
除了obj-m和obj-y两个目标之外，还有目标lib-y，这个目标下的所有文件会被用来生成静态库lib.a。在最终的链接阶段它会被链接进vmlinux。
此外lib.a还会被发布供更多的应用程序使用。

\subsubsection{编译选项}
编译选项的管理使用与.o文件类似的管理方式。
例如

此外KBUILD Makefile还提供了仅仅针对文件的编译方法。
\begin{lstlisting}[language=make]
 CFLAGS_aha152x.o = -DAHA152X_STAT -DAUTOCONF
\end{lstlisting}
这条CFLAGS仅仅用于当前subdir下的文件aha152x.o的生成。

\subsection{kbuild Commands}
kbuild系统中提供了很有常用的宏，链接它们对于阅读Linux Makefile非常有帮助。
\subsubsection{if\_changed}
可能读者会非常疑惑，前面提到，按道理Makefile会自己监控依赖项。如果依赖项变化了，Makefile会自己发现并重新生成直接依赖或者间接依赖次项的内容。那为什么还需要if\_chaneged这种功能呢？由GNU make自己来作者事情不是更好吗。
这是由于Linux的Makefile并没有按照最经典的依赖方式组织代码，这就造成了make无法监控更新，不得已需要有一个if\_changed这样的函数。
if\_changed有一个隐含约束，他检查的内容必须保存在变量\$\{targets\}当中。
\begin{lstlisting}[language=make]
 target: source(s) FORCE
     $(call if_changed,ld/objcopy/gzip)
\end{lstlisting}

Linux支持多种架构同时也支持多种编译器，但不同的编译器很多选项是不一样的，某些不同的编译器支持也不同。kbuild提供了一组函数用于判断当前使用的编译器是否支持指定选项。
这些函数包括cc\-option、cc\-option\-yn、cc\-option\-align、cc\-version等等。
在很多Makefile当中都可以找到使用它们的例子，如arch/i386/Makefile。
\begin{lstlisting}[language=make]
cflags-y += $(call cc-option,-march=pentium-mmx,-march=i586)
\end{lstlisting}
上面的代码会判断编译器是否支持“-march=pentium-mmx“，如果支持则使用。如果不支持就用“-march=i586”选项代替。

后面我们会尝试实现一个类似的Build系统，但由于我们只用GCC，这些函数我们就不用啦。为了方便读者自己阅读Linux的代码，我们在这里还是要顺便说明一下。


它们的实现在根目录的Makefile当中，基本都是通过shell命令完成的，如cc-option的实现如下。
\begin{lstlisting}[language=make]
cc-option = $(shell if $(CC) $(CFLAGS) $(1) \
               -S -o /dev/null -xc /dev/null \
               > /dev/null 2>&1; then \
               echo ``$(1)''; else echo ``$(2)''; fi ;)

\end{lstlisting}

\section{kbuild核心流程}
当用户在linux源代码目录下执行make的时候，构建系统就开始运作了。最开始执行的就是源代码根目录下的Makefile，linux的文档管它叫'top level Makefile'。
简单的说它包括如下共呢
\begin{compactenum}
\item 配置内核并产生.config文件
\item 生成include/linux/version.h文件
\item 根据目标arch的类型建立include/asm软链接
\item 更新所有的依赖
\item 递归的便利所有在init-*, core* drivers-* net-* libs*并build所有目标
\item 将生成的文件链接成vmlinux并存放在源代码根目录下
\item 最后准备用于安装的文件
\end{compactenum}

\subsection{变量初始化}
\subsubsection{LDFLAGS}
它是通用链接选项，所有的ld命令都会是用它
\subsubsection{LDFLAGS\_MODULE}
此选项用于生成可加载模块.ko的ld命令
\subsubsection{LDFLAGS\_vmlinux}
顾名思义，它是用于链接vmlinux的选项
\subsubsection{OBJCOPYFLAGS}
某些链接器不支持binary格式，此时可以通过objcopy命令来实现。例如
\begin{lstlisting}[language=bash]
OBJCOPYFLAGS := -O binary
# arch/i386/boot/Makefile
$(obj)/image: vmlinux FORCE
    $(call if_changed,objcopy)
\end{lstlisting}
\subsubsection{AFLAGS}
汇编程序的通用选项
\subsubsection{CFLAGS}
通用的C语言编译选项
\subsubsection{CFLAGS\_KERNEL}
生成内核的通用编译选项
\subsubsection{CFLAGS\_MODULE}
生成可加载模块的编译选项
\subsection{递归编译所有subdir}
真正的构建开始了，top level Makefile会按照一定次序递归访问subdir下的Makefile。这些子目录通过以下几个变量定义:
head-y、init-y、core-y、libs-y、drivers-y、net-y
\begin{compactenum}
\item head-y: 所有需要首先放到vmlinux的目标
\item libs-y: 所有用来生成lib.a的目录
\end{compactenum}
这些变量的定义分成两个部分，一部分在top level Makefile, 它定义了所有与平台架构无关的目录。另外一部分与平台相关的则定义在arch/\$\{ARCH\}/Makefile当中。
\begin{lstlisting}[language=make]
core-y += arch/i386/kernel/
libs-y += arch/i386/lib
drivers-$(CONFIG_OPROFILE)  += arch/sparc64/oprofile/
\end{lstlisting}

\subsubsection{链接文件的顺序}
这里顺便提一句，对于.o文件来说，顺序不是一个太重要的事情。但对于.lib文件来说，顺序就非常重要了。举个例子，假设我们有3个上述a, b, c分别定义在库文件a.lib, b.lib和c.lib当中。它们的调用关系为a调用b, b调用c。也可以用依赖关系来表达它们: a依赖b， b依赖c。
如果我们希望将a.lib，b.lib，c.lib链接成为一个可执行程序，那么必须以a b c的顺序出现
\begin{lstlisting}{language=bash}
  ld a.lib b.lib c.lib -o exe
\end{lstlisting}
如果以其他顺序出现，那么ld会报undefined symbol错误。这是因为ld总是认为找不到的符号会在后面的文件出现，它是不会向前查找的。
\subsection{生成vmlinux}

除了构建流程之外，kbuild还有clean流程，这部分比较简单，大家可以参考文档。
\subsection{一些技巧}
上面介绍的是kbuild Makefile的整体，除了这些之外，它还用到了很多有趣的技巧。
\subsubsection{verbose}
verbose这个词用过linux的人一定非常熟，基本上所有的shell命令都有一个-v选项，它会把很多信息打出来。kbuild Makefile也提供了这样的功能，它的实现非常简单，但却值得看一下。

首先需要大家明白的一点是@， 它是常用的shell技巧，在make执行的时候如果不希望shell显示当前正在运行的命令就在前面加上@。例如
\begin{lstlisting}[language=make]
@echo "abc"
\end{lstlisting}
如果加上@, 他的输出就是
\begin{lstlisting}[language=bash]
"abc"
\end{lstlisting}
如果不加上@, 那么他的输出就是
\begin{lstlisting}[language=bash]
echo "abc"
"abc"
\end{lstlisting}

在顶层Makefile当中，你会发现下面一段代码
\begin{lstlisting}[language=make]
ifeq ($(KBUILD_VERBOSE),1)
  quiet =
  Q =
else
  quiet=quiet_
  Q = @
endif
\end{lstlisting}
在其他地方，如果Makefile用到了shell命令一定会写成

\begin{lstlisting}[language=make]
%.s: %.c scripts FORCE
    $(Q)$(MAKE) $(build)=$(@D) \$@
\end{lstlisting}
\subsection{强制重新生成}
在kbuild Makefiles当中，你可能经常会发现一个依赖项FORCE, 它其实是用来强制重新生成的。如果加上这个依赖项无论其他依赖上如何，目标都将被重新生成。例如在顶层Makefile当中。
\begin{lstlisting}[language=make]
include/linux/version.h: $(srctree)/Makefile FORCE
        $(call filechk,version.h)
\end{lstlisting}
无论何时make执行到它都一定会执行，那么FORCE是如何实现的呢，简单到难以置信，它是一个伪目标。
\begin{lstlisting}[language=make]
FORCE:

\end{lstlisting}
它定义在顶层Makefile的最后一行。

\subsection{默认规则}
\begin{lstlisting}[language=make]
%.s: %.c scripts FORCE
  $(Q)$(MAKE) $(build)=$(@D) $@
%.i: %.c scripts FORCE
  $(Q)$(MAKE) $(build)=$(@D) $@
%.o: %.c scripts FORCE
  $(Q)$(MAKE) $(build)=$(@D) $@
%/:      scripts prepare FORCE
  $(Q)$(MAKE) KBUILD_MODULES=$(if $(CONFIG_MODULES),1) $(build)=$(@D)
%.lst: %.c scripts FORCE
  $(Q)$(MAKE) $(build)=$(@D) $@
%.s: %.S scripts FORCE
  $(Q)$(MAKE) $(build)=$(@D) $@
%.o: %.S scripts FORCE
  $(Q)$(MAKE) $(build)=$(@D) $@
\end{lstlisting}

\section{简化kbuild}
本着山寨的精神，Linux的Makefile也是需要改造一把的。不是为了让他变得更好，只是为了让它变得更简单，更容易理解。下面的章节将介绍kbuild系统的如何实现，当然那些高级配置部分的实现被省去了，作为一个仅仅支持x86的简陋操作系统，实在不需要配置啊。

\subsection{生成built-in}
kbuild通过脚本的方式将所有的obj-y文件通过\${LD} \-r当方式连接成为built\-in.o文件。
之后built\-in.o会被链接成为vmlinux文件。
有一点需要注意的时，obj\-y当中的文件是可以重复出现的，但仅仅第一次出现是有效的。
不过前面提到过这些文件对应的源代码文件有是汇编程序也有可能是C程序，它们是如何确定源文件路径及名称的。又是如何确定编译选项的？
下面的代码会给你答案。

\section{稍许改造}
\subsection{sysmap}
为了调试方便Linux在生成玩vmlinux之后，还通过nm命令生成了一张记录所有符号地址的表保存在文件当中。我们不打算这么做，毕竟去查表也是挺复杂的一件事情，为了方便起见我们还是使用gdb调试。因为我们在链接阶段生成两个版本的vmlinux。一个包含调试信息，它仅仅用来帮助调试器识别符号及变量。另外一个文件不太调试信息它将被写入到磁盘当中作为操作系统运行。无论生成那种文件，在编译阶段都通过选项-g使其包含调试信息。在链接不包含调试信息的版本是用 ld -s(或者--strip-all) 生成即可。--strip-all选项会取出所有符号相关的信息。
\subsection{输出路径}
很多年以前开始使用VC，它倾向于将生成的代码放在一个名为debug(或者release)的目录下以便于管理。
后来Visual Studio用的多了，发现这并不是一个很好的特性。
随着Solution的复杂度提升，更多的项目加入到了其中，每一个项目都有自己一个debug和release目录。
当我看到chromium的项目组织的时候，我非常的钦佩，我认为那是更好的输出文件组织形式。


\section{Linux目录结构}
\dirtree{%
.1 rlinux.
.2 arch.
.3 i386.
.3 mm.
.3 kernel.
.3 mach-default.
.3 boot.
.2 drivers.
.2 fs.
.2 include.
.3 asm.
.3 asm-i386.
.3 linux.
.2 init.
.2 kernel.
.3 timers.
.2 lib.
.2 mm.
.2 scripts.
}
